/**
 *  Copyright (c) Filipe Manana <fdmanana@gmail.com>
 *  All rights reserved.
 * 
 *  This file is part of jsdet.
 *
 *  jsdet is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published
 *  by the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  jsdet is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with jsdet.  If not, see <http://www.gnu.org/licenses/>.
 *
 */


package jsdet.parsing;

import java.util.*;

import jsdet.parsing.enums.ClassMemberType;

/**
 * Accumulates parsing statistics for a single unit (file) of JavaScript code.
 *
 * @author Filipe Manana
 *
 */
public class ParsingStats {

    private Set<String> functions = new LinkedHashSet<String>();
    private Set<String> classes = new LinkedHashSet<String>();
    private Set<String> closureObjects = new LinkedHashSet<String>();

    // maps a class name to the name of its base class
    private Map<String, String> classBase = new HashMap<String, String>();

    // maps a class name to a list of derived classes
    private Map<String, Set<String>> classDerived = new LinkedHashMap<String, Set<String>>();

    private Map<String, Set<String>> classMembers = new LinkedHashMap<String, Set<String>>();

    // keys are formed as a combination of class name and member name
    private Map<String, ClassMemberType> classMembersTypes = new HashMap<String, ClassMemberType>();

    private Map<String, Set<String>> closureObjectMembers = new LinkedHashMap<String, Set<String>>();

    // keys are formed as a combination of closure object name and member name
    private Map<String, ClassMemberType> closureObjectMembersTypes = new HashMap<String, ClassMemberType>();


    public ParsingStats() {
    }


    public Set<String> getFunctions() {
        return functions;
    }


    public boolean hasFunction(String function) {
        return functions.contains(function);
    }


    public Set<String> getClosureObjects() {
        return closureObjects;
    }


    public boolean hasClosureObject(String closureObject) {
        return closureObjects.contains(closureObject);
    }


    public Set<String> getClasses() {
        return classes;
    }


    public boolean hasClass(String className) {
        return classes.contains(className);
    }


    public String getClassBase(String className) {
        return classBase.get(className);
    }


    public Set<String> getDerivedClasses(String className) {
        Set<String> derived = classDerived.get(className);

        if (derived == null) {
            derived = new LinkedHashSet<String>();
        }

        return derived;
    }


    public void addFunction(String function) {
        removeClass(function);
        functions.add(function);
    }


    public void removeFunction(String function) {
        functions.remove(function);
    }


    public void addClosureObject(String closureObject) {

        removeClosureObject(closureObject);
        closureObjects.add(closureObject);
        closureObjectMembers.put(closureObject, new LinkedHashSet<String>());
    }


    public void removeClosureObject(String closureObject) {
        Set<String> members = getClosureObjectMembers(closureObject);

        for ( String member : members ) {
            removeClosureObjectMember(closureObject, member);
        }

        closureObjectMembers.remove(closureObject);
        closureObjects.remove(closureObject);
    }


    public void addClosureObjectMember(String closureObject, String member, ClassMemberType type) {

        if ( !hasClosureObject(closureObject) ) {
            addClosureObject(closureObject);
        }

        closureObjectMembersTypes.put(getClassMemberKey(closureObject, member), type);
        closureObjectMembers.get(closureObject).add(member);
    }


    public void removeClosureObjectMember(String closureObject, String member) {
        if ( hasClosureObject(closureObject) ) {
            closureObjectMembersTypes.remove(getClassMemberKey(closureObject, member));
            closureObjectMembers.get(closureObject).remove(member);
        }
    }


    public Set<String> getClosureObjectMembers(String closureObject) {
        Set<String> members = closureObjectMembers.get(closureObject);

        return ( members != null ) ? members : new LinkedHashSet<String>();
    }


    public Set<String> getClosureObjectMembers(String closureObject, ClassMemberType type) {
        Set<String> allMembers = getClosureObjectMembers(closureObject);
        Set<String> members = new LinkedHashSet<String>();

        for ( String member : allMembers ) {

            if ( isClosureObjectMemberOfType(closureObject, member, type) ) {
                members.add(member);
            }

        }

        return members;
    }


    public boolean hasClosureObjectMember(String closureObject, String member) {
        Set<String> allMembers = getClosureObjectMembers(closureObject);

        return allMembers.contains(member);
    }


    public ClassMemberType getClosureObjectMemberType(String closureObject, String member) {
        return closureObjectMembersTypes.get(getClassMemberKey(closureObject, member));
    }


    public boolean isClosureObjectMemberOfType(String closureObject, String member, ClassMemberType type) {
        ClassMemberType t = getClosureObjectMemberType(closureObject, member);

        return ( t == null ) ? false : t == type;
    }


    public void addClass(String className) {
        removeFunction(className);

        if ( !hasClass(className) ) {
            classes.add(className);
            classMembers.put(className, new LinkedHashSet<String>());
        }
    }


    public void removeClass(String className) {
        Set<String> members = getClassMembers(className);

        for ( String member : members ) {
            removeClassMember(className, member);
        }

        classMembers.remove(className);
        classes.remove(className);
    }


    public void addClassMember(String className, String member, ClassMemberType memberType) {

        addClass(className);

        Set<String> members = classMembers.get(className);

        members.add(member);
        classMembersTypes.put(getClassMemberKey(className, member), memberType);
    }


    public void removeClassMember(String className, String member) {

        if ( !hasClass(className) ) {
            return;
        }

        Set<String> members = classMembers.get(className);

        members.remove(member);
        classMembersTypes.remove(getClassMemberKey(className, member));
    }


    public void removeAllClassMembers(String className) {
        if ( !hasClass(className) ) {
            return;
        }

        Set<String> members = new HashSet<String>();
        // to avoid later on java.util.ConcurrentModificationException
        members.addAll(classMembers.get(className));

        for ( String member : members ) {
            removeClassMember(className, member);
        }
    }


    // doesn't consider inherited members!
    public boolean hasClassMember(String className, String member) {
        Set<String> allMembers = getClassMembers(className);

        return allMembers.contains(member);
    }


    // doesn't consider inherited members!
    public Set<String> getClassMembers(String className) {
        Set<String> members = classMembers.get(className);

        return ( members != null ) ? members : new LinkedHashSet<String>();
    }


    // doesn't consider inherited members!
    public Set<String> getClassMembers(String className, ClassMemberType ofType) {
        Set<String> allMembers = getClassMembers(className);
        Set<String> members = new LinkedHashSet<String>();

        for ( String member : allMembers ) {
            ClassMemberType type = getClassMemberType(className, member);

            if ( type == ofType ) {
                members.add(member);
            }
        }

        return members;
    }


    // doesn't consider inherited members!
    public ClassMemberType getClassMemberType(String className, String member) {
        return classMembersTypes.get(getClassMemberKey(className, member));
    }


    // doesn't consider inherited members!
    public boolean isClassMemberOfType(String className, String member, ClassMemberType type) {
        ClassMemberType memberType = getClassMemberType(className, member);

        return ( memberType == null ) ? false : ( memberType == type );
    }


    public void setBaseClass(String className, String baseClassName) {
        addClass(baseClassName);
        addClass(className);

        Set<String> derived = classDerived.get(baseClassName);

        if (derived == null) {
            derived = new LinkedHashSet<String>();
            classDerived.put(baseClassName, derived);
        }

        classBase.put(className, baseClassName);
        derived.add(className);
    }


    public List<String> getClassBaseHierarchy(String className) {
        List<String> list = new ArrayList<String>();
        String baseClass = classBase.get(className);

        while ( baseClass != null ) {
            list.add(baseClass);
            baseClass = classBase.get(baseClass);
        }

        return list;
    }


    public String findFirstUpperClassWithMember(String className, String member) {
        List<String> upperClasses = getClassBaseHierarchy(className);

        for ( String upperClass : upperClasses ) {

            if ( hasClassMember(upperClass, member) ) {
                return upperClass;
            }
        }

        return null;
    }


    public Set<String> getClassInheritedMembers(String className) {
        Set<String> inherited = new LinkedHashSet<String>();
        Set<String> ownMembers = getClassMembers(className);
        List<String> upperClasses = getClassBaseHierarchy(className);

        for ( String upperClass : upperClasses ) {
            Set<String> upperMembers = getClassMembers(upperClass);

            for ( String upperMember : upperMembers ) {
                if ( !ownMembers.contains(upperMember) ) {
                    inherited.add(upperMember);
                }
            }
        }

        return inherited;
    }


    public Set<String> getClassInheritedMembers(String className, ClassMemberType memberType) {
        Set<String> result = new LinkedHashSet<String>();
        Set<String> allInherited = getClassInheritedMembers(className);

        for ( String member : allInherited ) {
            String upperClass = findFirstUpperClassWithMember(className, member);

            if ( upperClass == null ) {
                // should never happen in this context
                continue;
            }

            if ( !isClassMemberOfType(upperClass, member, memberType) ) {
                continue;
            }

            result.add(member);
        }

        return result;
    }


    public Set<String> getClassOverridingMembers(String className) {
        Set<String> overrided = new LinkedHashSet<String>();
        Set<String> ownMembers = getClassMembers(className);
        List<String> upperClasses = getClassBaseHierarchy(className);

        for ( String member : ownMembers ) {
            for ( String upperClass : upperClasses ) {
                if ( hasClassMember(upperClass, member) ) {
                    overrided.add(member);
                    break;
                }
            }
        }

        return overrided;
    }


    public Set<String> getClassOverridingMembers(String className, ClassMemberType memberType) {
        Set<String> result = new LinkedHashSet<String>();
        Set<String> allOverrided = getClassOverridingMembers(className);

        for ( String member : allOverrided ) {
            String upperClass = findFirstUpperClassWithMember(className, member);

            if ( upperClass == null ) {
                // should never happen in this context
                continue;
            }

            if ( !isClassMemberOfType(upperClass, member, memberType) ) {
                continue;
            }

            result.add(member);
        }


        return result;
    }


    private String getClassMemberKey(String className, String memberName) {
        return className + "_" + memberName;
    }
}